The Kitchen Sink
by Robert Polic (robert@be.com)

What started as simple tutorial on how to do
context-sensitive menus and drag-n-drop in a list view has
turned into a light- weight application launcher. The full
source (all 520 lines including headers and comments) for
"EZ Launcher" can be found on our ftp site...

<ftp://ftp.be.com/pub/samples/r4/application_kit/EZLauncher.zip>

EZ Launcher is a simple BApplication that constructs a
single window with a scrolling list view containing icons
and labels for all applications in the /boot/apps folder.
Users can launch an application either by double-clicking
the item, right-clicking the mouse to access a
context-sensitive men, or dragging and dropping back onto
the window. All operations are done asynchronously to limit
the amount of time the window is locked (and therefor
unresponsive). Overkill for this app? You bet, but with the
BeOS, it's almost as simple to spawn a thread to handle
user actions as not to.

So I'll assume everyone here is familiar with constructing
an application and window and will skip over that part and
get to the meat, which in this case is the list. In this
app I'll use a scrolling BListView to both maintain my list
items and allow the user to select them...

TEZLauncherWindow::TEZLauncherWindow(BRect frame)
    :BWindow(frame, "EZ Launcher", B_TITLED_WINDOW,
        B_NOT_ZOOMABLE | B_WILL_ACCEPT_FIRST_CLICK)
{
    // set up a rectangle and instantiate a new view
    BRect		aRect(Bounds());
    BScrollView	*aScroller;

    // reduce by size of vertical scroll bar
    aRect.right -= B_V_SCROLL_BAR_WIDTH;
    // construct a BListView
    fList = new TEZLauncherView(aRect);
    // construct a scroll view containing the list view
    //and add it to the window
    AddChild(aScroller = new BScrollView("", fList,
        B_FOLLOW_ALL, B_WILL_DRAW, true,
        true, B_PLAIN_BORDER));
    BuildList();
}

BuildList is the method that actually adds all the items
from the /boot/apps directory to the list...

void TEZLauncherWindow::BuildList()
{
    BDirectory	dir;
    BEntry	entry;
    BPath		path;

    // walk through the apps directory adding
    //all apps to the list
    find_directory(B_APPS_DIRECTORY, &path, true);
    dir.SetTo(path.Path());
    // loop until we get them all
    while (dir.GetNextEntry(&entry, true) ==
            B_NO_ERROR)
    {
        if (entry.IsFile())
            // construct a new BListItem
            fList->AddItem(new TListItem(&entry));
    }
}

TListItem is derived from from BListItem and takes a single
parameter, an entry_ref. Through this entry_ref, TListItem
will extract and cache the application name and icon...

TListItem::TListItem(BEntry *entry)
    :BListItem()
{
    BNode	node;
    BNodeInfo	node_info;

    // try to get node info for this entry
    if ((node.SetTo(entry) == B_NO_ERROR) &&
        (node_info.SetTo(&node) == B_NO_ERROR)) {
        // cache name
        entry->GetName(fName);
        // create bitmap large enough for icon
        fIcon = new BBitmap(
            BRect(0, 0, B_LARGE_ICON - 1,
                    B_LARGE_ICON - 1), B_COLOR_8_BIT);
        // cache the icon
        node_info.GetIcon(fIcon);
        // adjust size of item to fit icon
        SetHeight(fIcon->Bounds().Height() +
                    kITEM_MARGIN);
        // cache ref
        entry->GetRef(&fRef);
    }
    else {
        fIcon = NULL;
        strcpy(fName, "<Lost File>");
        SetHeight(kDEFAULT_ITEM_HEIGHT);
    }
}

TListItem is also responsible for drawing the item in the
BListView's view...

void TListItem::DrawItem(BView *view, BRect rect,
				bool /* complete */)
{
    float		offset = 10;
    BFont		font = be_plain_font;
    font_height	finfo;

    // set background color
    if (IsSelected()) {
        // fill color
        view->SetHighColor(kSELECTED_ITEM_COLOR);
        // anti-alias color
        view->SetLowColor(kSELECTED_ITEM_COLOR);
    }
    else {
        view->SetHighColor(kLIST_COLOR);
        view->SetLowColor(kLIST_COLOR);
    }
    // fill item's rect
    view->FillRect(rect);

    // if we have an icon, draw it
    if (fIcon) {
        view->SetDrawingMode(B_OP_OVER);
        view->DrawBitmap(fIcon,
            BPoint(rect.left + 2, rect.top + 3));
        view->SetDrawingMode(B_OP_COPY);
        offset = fIcon->Bounds().Width() + 10;
    }

    // set text color
    (IsEnabled()) ?  view->SetHighColor(kTEXT_COLOR) :
            view->SetHighColor(kDISABLED_TEXT_COLOR);

    // set up font
    font.SetSize(12);
    font.GetHeight(&finfo);
    view->SetFont(&font);

    // position pen
    view->MovePenTo(offset,
        rect.top + ((rect.Height() - (finfo.ascent +
        finfo.descent + finfo.leading)) / 2) +
        (finfo.ascent + finfo.descent) - 2);
    // and draw label
    view->DrawString(fName);
}

All mouse actions are directed to our ListView and from here
we decide whether to display a context-sensitive menu, spawn
a task to see if we need to initiate a drag, or do nothing
and let the base class handle it...

void TEZLauncherView::MouseDown(BPoint where)
{
    uint32	buttons;

    // retrieve the button state from the
    // MouseDown message
    if (Window()->CurrentMessage()->FindInt32(
         "buttons", (int32 *)&buttons) == B_NO_ERROR)
    {
        // find item at the mouse location
        int32 item = IndexOf(where);
        // make sure item is valid
        if ((item >= 0) && (item < CountItems()))
        {
            // if clicked with second mouse button,
            // let's do a context-sensitive menu
            if (buttons & B_SECONDARY_MOUSE_BUTTON) {
                BPoint	point = where;
                ConvertToScreen(&point);
                // select this item
                Select(item);
                // do an async-popupmenu
                fMenu->Go(point, true, false, true);
                return;
            }
            // clicked with primary button
            else
            {
                int32 clicks;
                // see how many times we've
                //been clicked

                Window()->CurrentMessage()->
                        FindInt32("clicks", &clicks);
                // if we've only been clicked once
                // on this item, see if user
                // intends to drag
                if ((clicks == 1) ||
                    (item !CurrentSelection()))
                {
                    // select this item
                    Select(item);

                    // create a structure of
                    // useful data
                    list_tracking_data *data =
                        new list_tracking_data();
                    data->start = where;
                    data->view = this;

                    // spawn a thread that watches
                    // the mouse to see if a drag
                    // should occur.  this will free
                    // up the window for more
                    // important tasks

                    resume_thread(spawn_thread(
                        (status_t (*)(void *)) TrackItem,
                        "list_tracking",
                        B_DISPLAY_PRIORITY, data));
                    return;
                }
            }
        }
    }
    // either the user dbl-clicked an item or
    //clicked in an area with no
    // items.  either way, let BListView take care of it
    BListView::MouseDown(where);
}

If we've determined that mouse down was a single-click on
the item, we'll spawn a thread that monitors the mouse
position and if the mouse moves more than kDRAG_SLOP in any
direction, we'll initiate a DragMessage...

status_t TEZLauncherView::TrackItem(
                    list_tracking_data *data)
{
    uint32  buttons;
    BPoint  point;

    // we're going to loop as long as the mouse
    //is down and hasn't moved
    // more than kDRAG_SLOP pixels
    while (1) {
        // make sure window is still valid
        if (data->view->Window()->Lock()) {
            data->view->GetMouse(&point, &buttons);
            data->view->Window()->Unlock();
        }
        // not?  then why bother tracking
        else
            break;
        // button up?  then don't do anything
        if (!buttons)
            break;
        // check to see if mouse has moved more
        // than kDRAG_SLOP pixels in any direction
        if ((abs((int)(data->start.x - point.x))
                > kDRAG_SLOP) ||
        (abs((int)(data->start.y - point.y))
                > kDRAG_SLOP))
        {
            // make sure window is still valid
            if (data->view->Window()->Lock()) {
                BBitmap	 *drag_bits;
                BBitmap	 *src_bits;
                BMessage drag_msg(eItemDragged);
                BView    *offscreen_view;
                int32    index =
                    data->view->CurrentSelection();
                TListItem *item;

                // get the selected item
                item = dynamic_cast<TListItem *>
                    (data->view->ItemAt(index));
                if (item) {
                    // init drag message with
                    //some useful information
                    drag_msg.AddInt32("index",index);
                    // we can even include the item
                    drag_msg.AddRef("entry_ref",
                                    item->Ref());

                    // get bitmap from current item
                    src_bits = item->Bitmap();
                    // make sure bitmap is valid
                    if (src_bits)
                    {
                        // create a new bitmap based on
                        // the one in the list (we
                        // can't just use the bitmap we
                        // get passed because the
                        // app_server owns it after we
                        // call DragMessage, besides
                        // we wan't to create that cool
                        // semi-transparent look)
                        drag_bits = new BBitmap(
                            src_bits->Bounds(),
                            B_RGBA32, true);
                        // we need a view
                        // so we can draw
                        offscreen_view =
                            new BView(
                              drag_bits->Bounds(), "",
                              B_FOLLOW_NONE, 0);
                        drag_bits->
                            AddChild(offscreen_view);

                        // lock it so we can draw
                        drag_bits->Lock();
                        // fill bitmap with black
                        offscreen_view->
                            SetHighColor(0, 0, 0, 0);
                        offscreen_view->FillRect(
                            offscreen_view->Bounds());
                        // set the alpha level
                        offscreen_view->
                            SetDrawingMode(B_OP_ALPHA);

                        offscreen_view->
                            SetHighColor(0, 0, 0, 128);

                        offscreen_view->
                            SetBlendingMode(
                                B_CONSTANT_ALPHA,
                                B_ALPHA_COMPOSITE);
                        // blend in bitmap
                        offscreen_view->
                            DrawBitmap(src_bits);
                        drag_bits->Unlock();

                        // initiate drag from center
                        // of bitmap

                        data->view->DragMessage(
                          &drag_msg, drag_bits,
                          B_OP_ALPHA,
                          BPoint(
                          drag_bits->Bounds().Height()/2,
                          drag_bits->Bounds().Width()/2 ));
                    } // endif src_bits
                    else
                    {
                        // no src bitmap?
                        // then just drag a rect
                        data->view->DragMessage(&drag_msg,
                            BRect(0, 0, B_LARGE_ICON - 1,
                              B_LARGE_ICON - 1));
					}
                } // endif item
                data->view->Window()->Unlock();
            } // endif window lock
            break;
        } // endif drag start
        // take a breather
        snooze(10000);
    } // while button
    // free resource
    free(data);
    return B_NO_ERROR;
}

The only thing left is to wait for a launch message to
arrive at the window...

void TEZLauncherWindow::MessageReceived(BMessage *msg)
{
    char		string[512];
    int32		index;
    entry_ref	entry;
    entry_ref	*ref = NULL;
    status_t	result;
    TListItem	*item;

    switch (msg->what) {
        case eItemDblClicked:
            // item was dbl-clicked.
            //from the message we can find the item
            msg->FindInt32("index", &index);
            item = dynamic_cast<TListItem *>
                (fList->ItemAt(index));
            if (item)
                ref = item->Ref();
            break;

        case eItemMenuSelected:
            // item was selected with menu.
            //find item using CurrentSelection
            index = fList->CurrentSelection();
            item = dynamic_cast<TListItem *>
                (fList->ItemAt(index));
            if (item)
                ref = item->Ref();
            break;

        case eItemDragged:
            // item was dropped on us.
            //get ref from message
            if (msg->HasRef("entry_ref")) {
                msg->FindRef("entry_ref", &entry);
                ref = &entry;
            }
            break;

        default:
            BWindow::MessageReceived(msg);
    }
    if (ref) {
        // if we got a ref, try launching it
        result = be_roster->Launch(ref);
        if (result != B_NO_ERROR) {
            sprintf(string,
            "Error launching: %s", strerror(result));
            (new BAlert("", string, "OK"))->Go();
        }
    }
}





